package cn.xinfei.xdecision.engine.runner.executor.strategy.node;

import cn.xinfei.xdecision.common.model.component.decisiontree.DecisionTreeDetail;
import cn.xinfei.xdecision.common.model.component.decisiontree.vo.DecisionTreeVersionVo;
import cn.xinfei.xdecision.common.model.component.decisiontree.vo.DecisionTreeVo;
import cn.xinfei.xdecision.common.model.component.strategyout.StrategyOutput;
import cn.xinfei.xdecision.common.model.enginex.model.EngineNodeModel;
import cn.xinfei.xdecision.common.model.enginex.model.NodeJsonModel;
import cn.xinfei.xdecision.common.model.enginex.po.EngineNode;
import cn.xinfei.xdecision.common.service.component.decisiontree.DecisionTreeDetailService;
import cn.xinfei.xdecision.common.service.component.decisiontree.DecisionTreeService;
import cn.xinfei.xdecision.common.service.component.decisiontree.DecisionTreeVersionService;
import cn.xinfei.xdecision.common.service.datax.datamanage.FieldService;
import cn.xinfei.xdecision.common.utils.enums.enginex.ConditionValueTypeEnum;
import cn.xinfei.xdecision.common.utils.enums.enginex.NodeTypeEnum;
import cn.xinfei.xdecision.common.utils.util.JsonUtils;
import cn.xinfei.xdecision.common.utils.util.runner.jeval.EvaluationException;
import cn.xinfei.xdecision.common.vo.FieldInfoVo;
import cn.xinfei.xdecision.engine.runner.context.XDecisionContext;
import cn.xinfei.xdecision.engine.runner.executor.NodeHandler;
import cn.xinfei.xdecision.engine.runner.executor.XRunner;
import cn.xinfei.xdecision.engine.runner.executor.handler.DecisionTreeNodeHandler;
import cn.xinfei.xdecision.engine.runner.executor.model.StateTransmitModel;
import cn.xinfei.xdecision.engine.runner.executor.strategy.EngineNodeRunner;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static cn.xinfei.xdecision.constant.EngineResultEnum.EXCEPTION_NODE_NULL_END;

@XRunner
@Slf4j
@Service
public class DecisionTree extends EngineNodeRunner {

    @Autowired
    private DecisionTreeDetailService decisionTreeDetailService;

    @Autowired
    private FieldService fieldService;

    @Autowired
    private DecisionTreeService decisionTreeService;

    @Autowired
    private DecisionTreeNodeHandler decisionTreeNodeHandler;

    @Autowired
    private DecisionTreeVersionService decisionTreeVersionService;

    private static final String DECISION_TREE_CACHE_PRE = "dc_tr_cache_pre_";

    @Override
    public String getServiceName() {
        return EngineNodeRunner.PRE + NodeTypeEnum.DECISION_TREE.getType();
    }

    @Override
    public void before(NodeHandler nodeHandler) {

    }

    @Override
    public Map<String, StrategyOutput> getOutputFieldInfo(NodeHandler nodeHandler) {

        Map<String, StrategyOutput> strategyOutputMap = super.getOutputFieldInfo(nodeHandler);

        EngineNode engineNode = nodeHandler.getEngineNode();

        String nodeJson = engineNode.getNodeJson();
        if (StringUtils.isEmpty(nodeJson)) {
            return strategyOutputMap;
        }
        try {

            NodeJsonModel nodeJsonModel = JSONObject.parseObject(nodeJson, NodeJsonModel.class);
            List<EngineNodeModel> decisionTreeList = nodeJsonModel.getComponentList();
            if (null != decisionTreeList && decisionTreeList.size() > 0) {
                Map<String, FieldInfoVo> fieldInfo = metadataProvider.getFieldInfo();
                decisionTreeList.stream().forEach(decisionTable -> {
                    try {
                        List<DecisionTreeVersionVo> decisionTreeVersionVos = decisionTreeVersionService.queryVersionByTreeCodes(decisionTable.getCode(), decisionTable.getVersionNo());
                        for (DecisionTreeVersionVo decisionTreeVersionVo : decisionTreeVersionVos) {
                            String resultFieldCode = decisionTreeVersionVo.getResultFieldCode();

                            FieldInfoVo value = fieldInfo.get(resultFieldCode);
                            if (value != null) {
                                StrategyOutput output = new StrategyOutput();
                                output.setFieldCode(resultFieldCode);
                                output.setVarValueType(value.getValueType());//valuetype-->varvaluetype
                                //固定输出， 此时ValueType并不一定会在后续过程中起作用，一定会在runNode中给他负值，但会存在赋值时报错的可能，比如类型不匹配，所以设置为常量，在值缺失的时候取默认值
                                output.setValueType(ConditionValueTypeEnum.VARIABLE.getCode());
                                output.setFieldValue(decisionTreeVersionVo.getDefaultValue());
                                output.setStage(value.getStage());
                                output.setFixed(true);
                                strategyOutputMap.put(resultFieldCode, output);
                            } else {
                                log.warn("变量不存在,fieldCode={}", resultFieldCode);
                            }

                        }
                    } catch (Exception e) {
                        log.error("加载决策树信息异常，error={},engineNode={}", e.getMessage(), JsonUtils.toJsonString(engineNode), e);
                    }
                });
            }


        } catch (Exception e) {
            log.error("DecisionTablesNode,getNodeField:获取决策树指标异常,engineNode={},error={}", JsonUtils.toJsonString(engineNode), e.getMessage(), e);
        }
        return strategyOutputMap;
    }

    @Override
    public Map<String, FieldInfoVo> getInputFieldInfo(NodeHandler nodeHandler) {
        Map<String, FieldInfoVo> fieldVos = super.getInputFieldInfo(nodeHandler);
        EngineNode engineNode = nodeHandler.getEngineNode();
        String nodeJson = engineNode.getNodeJson();
        if (StringUtils.isEmpty(nodeJson)) {
            return fieldVos;
        }
        Set<String> fieldCodes = new HashSet<>();
        Set<Long> versionIds = new HashSet<>();
        try {

            NodeJsonModel nodeJsonModel = JSONObject.parseObject(nodeJson, NodeJsonModel.class);
            List<EngineNodeModel> decisionTreeList = nodeJsonModel.getComponentList();
            Map<String, FieldInfoVo> fieldInfo = metadataProvider.getFieldInfo();
            if (null != decisionTreeList && decisionTreeList.size() > 0) {
                decisionTreeList.forEach(decisionTable -> {
                    try {
                        Long versionId = decisionTable.getVersionId();
                        List<DecisionTreeDetail> detailVos = decisionTreeDetailService.queryByVersionId(versionId);
                        extractField(detailVos, fieldCodes);
                        DecisionTreeVo decisionTreeVo = decisionTreeService.queryExecuteDecisionTree(null, versionId);
                        nodeHandler.put(DECISION_TREE_CACHE_PRE + versionId, decisionTreeVo);
                        versionIds.add(versionId);
                    } catch (Exception e) {
                        log.error("加载决策树信息异常，error={},engineNode={}", e.getMessage(), JsonUtils.toJsonString(engineNode), e);
                    }
                });
            }


            fieldCodes.forEach(fieldCode -> {
                FieldInfoVo value = fieldInfo.get(fieldCode);
                if (value != null) {
                    fieldVos.put(fieldCode, value);
                }
            });

        } catch (Exception e) {
            log.error("DecisionTablesNode,getNodeField:获取决策树指标异常,engineNode={},error={}", JsonUtils.toJsonString(engineNode), e.getMessage(), e);
        }
        return fieldVos;
    }

    private void extractField(List<DecisionTreeDetail> details, Set<String> fieldCodes) {
        if (details != null && details.size() > 0) {
            for (int i = 0; i < details.size(); i++) {
                DecisionTreeDetail decisionTreeDetail = details.get(i);
                String fieldCode = decisionTreeDetail.getFieldCode();
                fieldCodes.add(fieldCode);
                List<DecisionTreeDetail> children = decisionTreeDetail.getChildren();
                extractField(children, fieldCodes);
            }
        }
        return;
    }


    @Override
    public StateTransmitModel runNode(NodeHandler nodeHandler) throws EvaluationException {

        EngineNode engineNode = nodeHandler.getEngineNode();
        String nodeCode = engineNode.getNodeCode();
        //监控中心--记录节点快照信息
        Set<Long> versionIdList = new HashSet<>();


        String nodeJson = engineNode.getNodeJson();
        if (StringUtils.isEmpty(nodeJson)) {
            log.error("DecisionTree node json is null");
            return XDecisionContext.getContext().getStateContext().setEngineResultEnum(EXCEPTION_NODE_NULL_END);
        }
        NodeJsonModel nodeJsonModel = JSONObject.parseObject(nodeJson, NodeJsonModel.class);
        List<EngineNodeModel> decisionTreeList = nodeJsonModel.getComponentList();
        decisionTreeList.stream().forEach(decisionTable -> {
            Long versionId = decisionTable.getVersionId();
            versionIdList.add(versionId);
        });
        String nextNodeCode = nodeHandler.getEngineNode().getNextNodeCodes();

        /**
         * 决策树节点只能选择一个决策树实例
         * versionIdList.size=1
         */
        for (Long versionId : versionIdList) {

            DecisionTreeVo decisionTreeVo = (DecisionTreeVo) nodeHandler.get(DECISION_TREE_CACHE_PRE + versionId);
            if (decisionTreeVo == null) {
                continue;
            }
            DecisionTreeVersionVo version = decisionTreeVo.getExecuteVersion();
            String resultFieldEn = version.getResultFieldCode();
            //执行决策表
            Object executeResult = decisionTreeNodeHandler.executeDecisionTree(version);

            if (null == executeResult) {
                //未命中输出，默认值
                executeResult = version.getDefaultValue();
                log.info("决策树执行结果-默认值，nodeCode={},{}={}", nodeCode, resultFieldEn, executeResult);
            }
            setOutput(nodeHandler, resultFieldEn, executeResult);
            log.info("决策树执行结果，nodeCode={},{}={}", nodeCode, resultFieldEn, executeResult);
        }
        return XDecisionContext.getContext().getStateContext().setNextNodeCode(nextNodeCode);
    }


}
